package IC.lir;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import IC.AST.ASTNode;
import IC.AST.DefaultVisitor;
import IC.AST.PropogatingVisitor;
import IC.AST.nodes.*;
import IC.Analyzer.SymTabEntry;
import IC.Analyzer.Types.MethodType;
import IC.Analyzer.Types.Type;
import IC.Analyzer.Types.VoidType;

/**
 * Prepare:
 *	1. Extract string literals
 *	2. Build class dispatch vectors
 *	3. build class field layouts
 *	4. remember l-values
 *
 */

public class PreProcessor extends DefaultVisitor<Object,Object>
implements PropogatingVisitor<Object,Object> 
{
	private Map<String,String>		stringLiterals;
	private int						strCounter;
	private	Program					root;
	
	/**
	 * private constructor
	 */
	protected PreProcessor(Program root) {
		this.root = root;
		strCounter = 1;
	}
	
	/**
	 * PreProcess a type-checked AST
	 * @param prog the program AST
	 * @param classInfoMap map of classes
	 * @param stringLiterals map of string literals
	 */
	public static void Process(Program prog) 
	throws Exception 
	{
		PreProcessor p = new PreProcessor(prog);
		prog.accept(p, null);
	}
	
	/**
	 * visit a program and print its contents
	 */
	public Object visit(Program that, Object context) 
	throws Exception {
		this.stringLiterals = that.getStringLiterals();
		for (ClassDecl cls: that.getClasses()) {
			cls.accept(this,context);
		}
		return null;
	}
	
	/**
	 * visit a class declaration
	 * create ClassInfo object for the class
	 * iterate over all class members putting them in classInfoMap
	 */
	public Object visit(ClassDecl that, Object context) 
	throws Exception {
		if (! that.getClassID().equalsIgnoreCase("Library")) {
			ClassInfo classInfo =  new ClassInfo(that);
			for (ASTNode cmd : that.getClassMembers()) {
				cmd.accept(this,classInfo);
			}
		}
		return null;
	}
	
	/**
	 * visit Method declaration, add it to the correct ClassInfo
	 */
	public Object visit(MethodDecl that, Object context) 
	throws Exception {
		ClassDecl classDecl = ((ClassInfo)context).getClassDecl();

		// set label
		String labelName;
		if (that == root.getMain()) {
			labelName = "_ic_main";
		}
		else {
			labelName = "_"+classDecl.getClassID()+"_"+that.getMethodName();
		}
		that.setLabel(labelName);
	
		// check if static? 
		if(that.isStatic()) {
			if (that.getBody() != null) {
				that.getBody().accept(this, null);
			}
			return null;
		}
		
		// get formals
		List<Type> argTypes = new ArrayList<Type>();
		for (FormalDecl fd: that.getFormals()) {
			argTypes.add(fd.getType().getType());
		}

		// get return type
		Type retType = (that.getReturnType() == null)?VoidType.getIt():that.getReturnType().getType();
		
		MethodType myType = MethodType.getIt(retType, argTypes);
		String id = that.getMethodName();

		SymTabEntry currClassSymTab = classDecl.getSymTab().resolve("this");

		ClassDecl currClassDecl = (ClassDecl)currClassSymTab.getDeclNode();
		
		String baseClassID = currClassDecl.getBaseClassID();
		SymTabEntry e;
		if(baseClassID != null) {
			SymTabEntry baseClassSymTab = that.getSymTab().resolve(baseClassID);
			e = baseClassSymTab.getDeclNode().getSymTab().resolve(id);
		}
		else {
			e = null;
		}
		
		if (e == null) { // no overriding
			
			assert(currClassSymTab.getDeclNode() instanceof ClassDecl);
			
			((ClassInfo)context).addMethod(that, classDecl);
			if (that.getBody() != null) {
				that.getBody().accept(this, null);
			}
		}
		else if(e.getType().equals(myType)) {  // signatures match, overriding
			
			assert(currClassSymTab.getDeclNode() instanceof ClassDecl);

			SymTabEntry baseClassSymTab = that.getSymTab().resolve(baseClassID);
			
			ClassDecl baseClassDecl = (ClassDecl)baseClassSymTab.getDeclNode();
			
			((ClassInfo)context).overrideMethod(baseClassDecl, currClassDecl, (MethodDecl)e.getDeclNode(),that);
			
			// visit the body of just return null
			if (that.getBody() != null) {
				that.getBody().accept(this, context);
			}
		}
		return null;
	}

	/**
	 *  visit Fields declaration, add it to the correct ClassInfo
	 */
	public Object visit(FieldsDecl that, Object context) 
	throws Exception {
		for(String id : that.getIDs()) {
			((ClassInfo)context).addField(id);
		}
		return null;
	}

	
	/**
	 * Set the destination vaiable as an l-value
	 */
	public Object visit(AssignStmt that, Object context) 
	throws Exception {
		that.getDest().accept(this, context);
		that.getDest().setToLvalue();
		that.getSrc().accept(this, context);
		return null;
	}


	/**
	 * Set the destination vaiable as an l-value
	 */
	public Object visit(VarDeclStmt that, Object context) 
	throws Exception {
		if (that.getInit() != null) {
			that.getVar().setToLvalue();
			that.getInit().accept(this,context);
		}
		return null;
	}

	
	/**
	 * add the literal to the map, if not existed before
	 */
	public Object visit(StringLiteral that, Object context) 
	throws Exception {
		
		String	value = that.getValue();
		if (null == stringLiterals.get(value)) {
			String name = "str" + (strCounter++);
			stringLiterals.put(value, name);
		}
		return null;
	}
}
